/*
Copyright 2014 Aaron Batilo

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

//@author: Aaron Batilo

//This class will represent a compressed file instance. As such, we can write the compressed file to the disk
//Alternatively, if we have a compressed file instance, we can decompress it and write the raw file to the disk

#include "CompressedFile.h"

//These are the private member variables used within this class
RawFile myRawFile;
Key myKey;

//This constructor will take in a CMP and read they key from the paired KEY file
CompressedFile::CompressedFile(string inFileName) : File(inFileName, FileType::CMP)
{
	readKeyFromFile();
}

//This constructor is used when passing in a raw TXT file for compression
CompressedFile::CompressedFile(RawFile inRawFile)
{
	myRawFile = inRawFile;
	myKey = myRawFile.createKey();
}

CompressedFile::~CompressedFile()
{
}

//Reads the key file and populates the key member variable
void CompressedFile::readKeyFromFile()
{
	File file(File::getName());
	File keyFile(File::getNameNoExtension() + ".KEY");
	Key key;
	key.createFromFile(keyFile);
	myKey = key;
}

//This method will compress the raw data and write the compressed data to the disk
void CompressedFile::compress()
{
	//Create the output file stream to the CMP file we will create
	ofstream compFileInStream(myRawFile.getNameNoExtension() + ".CMP", ios::out | ios::binary);

	//Store the contents of the raw TXT file
	string rawFileContents = myRawFile.readContents();

	//Split the file contents with a space for the delimiter
	vector<string> splitWords = StringUtility::split(rawFileContents, ' ');

	//Check to see if the stream is validly open
	if (compFileInStream.is_open())
	{
		//Loop through every word from the raw file
		for (int i = 0; i < splitWords.size(); i++)
		{
			//This variable is used for readability
			string currentWord = splitWords[i];

			//Get the byte pair key value, it will return '\0' if there is no pair
			unsigned char bytePairKey = myKey.getStringToIntKeyValue(currentWord);

			//Determine whether or not we have an unidentified word
			if (bytePairKey != '\0')
			{
				//This byte is the byte that we write into the CMP file
				char bytePairKeyByte[1] = { bytePairKey };
				compFileInStream.write(bytePairKeyByte, 1);
			}
			else
			{
				//This byte will tell us that the following word is not in our table
				unsigned char nullByte = '\0';
				//Store the length of the word as a byte to store later
				unsigned char wordLength = strlen(currentWord.c_str());

				//This is the string to write to the CMP file
				string nonencodedString = string(1, nullByte) + string(1, wordLength) + currentWord;

				//Encode the string as a character array
				char *encodedBytes = StringUtility::toCharArray(nonencodedString);

				//Note, we can't use strlen on nonencodedString because strlen ends at '\0'
				//This means that strlen would return 0, however we know that nonencodedString is
				//the length of the word we stored earlier, plus the null byte, plus the length byte
				//We can safely assume it's wordLength + 2 more bytes
				compFileInStream.write(encodedBytes, wordLength + 2);
			}
		}
		//Close the file stream which writes the disk to the file
		compFileInStream.close();
	}
}

//This method takes a compressed file instance and will write to the disk its uncompressed version
void CompressedFile::decompress()
{
	//This stream goes out to the TXT file we'll be creating
	ofstream rawFileOutStream(File::getNameNoExtension() + ".TXT");

	//This stream is the stream being read in from the CMP file we're decompressing
	ifstream compFileInStream(File::getNameNoExtension() + ".CMP", ios::binary | ios::in);

	//Read the file all the way to the end
	compFileInStream.seekg(0, ios::end);
	//Store the size of the CMP file in bytes
	int size = compFileInStream.tellg();
	//Put the in stream pointer back to the beginning of the file
	compFileInStream.seekg(0, ios::beg);

	//This is the number of read bytes so we know how much to read from the CMP file
	int readBytes = 0;

	//Continue to read through the CMP file until we've read through all of it
	while (readBytes < size)
	{
		//This is the place holder for our signal byte
		char compCharByte[1];

		//Read the byte and populate the older
		compFileInStream.read(compCharByte, 1);

		//Convert the char array to a singular char
		unsigned char compKeyChar = compCharByte[0];

		//Store the char as an integer for use as an index later
		unsigned int compKeyInt = compKeyChar;

		//Increment our read byte count by one
		readBytes++;

		//Check to see if we have our null byte 
		if (compKeyChar != '\0')
		{
			//Since we did not fine the null terminator character, we know that we have a byte pair key
			//We can just go ahead and retrieve the word paired with the byte from our key
			string bytePairWord = myKey.getIntToStringKeyValue(compKeyInt);

			//Write the word to our TXT stream
			rawFileOutStream << bytePairWord + " ";
		}
		else
		{
			//Since we did find our null terminator character, we know the next byte will be the length
			//of the word that follows
			char compLengthByte[1];

			//Read this byte in
			compFileInStream.read(compLengthByte, 1);

			//Convert the byte from the char array to a char
			char compWordLengthByte = compLengthByte[0];
			
			//Convert that byte to an int
			//This logic works assuming the word isn't more than 255 characters long
			unsigned int compWordLength = compWordLengthByte;

			//Increment the byte count again since we read in the single length byte
			readBytes++;

			//This buffer represents a buffer that will store the word
			char wordBuffer[255];

			//Read the file's bytes into the buffer
			compFileInStream.read(wordBuffer, compWordLength);

			//Since we allocated 255 of bytes space, we know that
			//assuming the word is not 255 characters long we
			//will have extra filler bytes
			string bytePairValueUntrimmed(wordBuffer);

			//Trim the string of the excess bytes
			string bytePairTrimmed(bytePairValueUntrimmed.substr(0, compWordLength));

			//Add these read bytes to our read byte count
			readBytes += compWordLength;

			//Write these words to the TXT file stream
			rawFileOutStream << bytePairTrimmed + " ";
		}
	}
	//Close the file streams that we used
	rawFileOutStream.close();
	compFileInStream.close();
}